package com.easychat.websocket.netty;

import com.easychat.entity.dto.TokenUserInfoDto;
import com.easychat.redis.RedisCache;
import com.easychat.redis.RedisComponent;
import com.easychat.utils.StringTools;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;

/**
 * @Author Dongdong
 * @注释 自定义handler
 */
@Component("handlerWebSocket")
//@Shareable 修饰 Handler，保证 Handler 是可共享的，避免每次都创建一个实例
@ChannelHandler.Sharable
public class HandlerWebSocket extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    private static final Logger logger = LoggerFactory.getLogger(HandlerWebSocket.class);
    @Resource
    private RedisComponent redisComponent;
    @Resource
    private ChannelContextUtils channelContextUtils;
    @Resource
    private RedisCache redisCache;

    private TokenUserInfoDto tokenUserInfoDto;
    // 连接建立，返回消息
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        super.handlerAdded(ctx);
        logger.info("handlerAdded");
    }

    // 接收消息
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame twsf) throws Exception {
        Channel channel = ctx.channel();
        // 获取userId
        Attribute<String> attribute = channel.attr(AttributeKey.valueOf(channel.id().toString()));
        String userId = attribute.get();
//        logger.info("收到userId={}的客户端消息：{}",userId,twsf.text());
        redisComponent.saveHeartBeat(userId);
    }

    /*
    认证， 校验 token的方法
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        // 握手完成
        if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
            WebSocketServerProtocolHandler.HandshakeComplete complete =
                    (WebSocketServerProtocolHandler.HandshakeComplete) evt;

            // 获取请求的url
            String url = complete.requestUri();
            logger.info("url:{}", url);// /ws?token=123
            String token = getToken(url);
            if (token == null) {
                ctx.channel().close();// 关闭通道
                return;
            }
            tokenUserInfoDto = redisComponent.getTokenUserInfoDto(token);
            if (tokenUserInfoDto == null) {
                ctx.channel().close();// 关闭通道
                return;
            }
               /*  认证成功，将通道和用户信息绑定 存储到redis中，
                 redisComponent.setChannelUserInfo(ctx.channel(), tokenUserInfoDto);
                看似可以，但是实际不行，因为ctx.channel()无法序列化，所以无法存储到redis中，只能存储到内存中
                如果存储到内存，那么在集群中，就涉及到数据共享问题。
                */

            channelContextUtils.addContext(tokenUserInfoDto.getUserId(), ctx.channel());

        }

    }

    private String getToken(String url) {
        if (StringTools.isEmpty(url) || -1 == url.indexOf("?")) {
            return null;
        }
        /*
        String url = "https://example.com/path?param1=value1&param2=value2";
        String[] queryParams = url.split("\\?");

        queryParams[0] 现在包含 "https://example.com/path"
        queryParams[1] 现在包含 "param1=value1&param2=value2"
        如果url中没有问号，queryParams将只有一个元素，即整个url字符串
         */
        String[] queryParams = url.split("\\?");
        if (queryParams.length < 2) {
            return url;
        }
        String[] params = queryParams[1].split("=");
        if (params.length != 2) {
            return url;
        }
        return params[1];
    }

    /**
     * 通道就绪后触发，表示通道已经建立，用作初始化
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        redisCache.setCacheObject("Online"+tokenUserInfoDto.getUserId(),new Date());
        logger.info("websocket连接成功，有新的连接加入。。。");
    }

    /*
     * 通道断开后触发，表示通道已经关闭
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        redisCache.deleteObject("Online"+tokenUserInfoDto.getUserId());
        logger.info("websocket断开，有连接断开。。。");
        channelContextUtils.removeContext(ctx.channel());

    }

}
